package rpc

/*
基于 HTTP 协议的 RPC 客户端（HttpClient），属于rpc包
功能：通过HTTP POST请求与远程RPC服务通信，支持远程方法调用和消息广播
依赖：标准库的http客户端、json序列化、上下文管理等
该代码通过 HTTP 协议实现了 RPC 通信的客户端，核心是将 RPC 消息通过 JSON 序列化后在 HTTP 层传输。
Call方法用于点对点的远程方法调用，Broadcast用于向集群节点广播消息，适用于对实时性要求不极致但需要跨语言 / 跨服务通信的场景
（如游戏服务器的跨节点通知、微服务间的协作）。

单点：指整个服务仅由一个节点提供（如单台服务器）。所有 RPC 请求都直接发送到这一个节点，没有其他备份或同类节点。
集群：指服务由多个相同功能的节点组成（如多台服务器共同提供游戏服务）。RPC 请求可能需要分发到某个节点，或需要同步到所有节点（广播）。

*/

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

/* //* RPC客户端实现，基于HTTP协议与远程服务通信，提供Call（点对点调用）和Broadcast（集群广播）方法，支持JSON序列化和请求超时控制 */

// 基于http的rpc客户端
// 用于通过HTTP协议与远程RPC服务进行通信
type HttpClient struct {
	baseURL string       // 远程RPC服务的基础URL（如"http://localhost:8080"）
	client  *http.Client // HTTP客户端实例，用于发送HTTP请求
}

// 创建http rpc 客户端
//
//	参数：baseURL - 远程服务的基础URL（格式：协议://地址:端口）
//	返回：初始化后的HttpClient指针
func NewHttpClient(baseURL string) *HttpClient {
	return &HttpClient{
		baseURL: baseURL, // 初始化基础URL
		client: &http.Client{
			Timeout: 10 * time.Second, // 设置HTTP请求超时时间为10秒，避免请求无限阻塞
		},
	}
}

// 调用远程方法
func (c *HttpClient) Call(ctx context.Context, route uint32, data []byte) ([]byte, error) {
	// 构建RPC消息并序列化为JSON
	// 将路由和数据打包成Message结构体，再转为JSON字节流（HTTP传输需要文本格式）
	reqData, err := json.Marshal(Message{
		Route: route,
		Data:  data,
	})
	if err != nil {
		return nil, err
	}
	// 发送HTTP POST请求到远程RPC服务的调用接口
	// 目标URL：baseURL + "/rpc/call"（服务端需对应实现该接口）
	// 请求体：JSON序列化后的Message
	// 请求头：Content-Type设为application/json，告知服务端数据格式
	resp, err := c.client.Post(
		fmt.Sprintf("%s/rpc/call", c.baseURL), // 拼接完整请求URL
		"application/json",                    // 指定请求体格式为JSON
		bytes.NewReader(reqData),              // 将JSON字节流转为io.Reader（http.Post要求的请求体类型）
	)
	// 理论上，这里应该也用StatusOK，不过这里既然是单点，出错直接用err就行
	if err != nil {
		return nil, err
	}
	// 延迟关闭响应体，确保资源释放（即使函数提前返回也会执行）
	defer resp.Body.Close()
	// 解析响应
	var respMsg Message
	// 使用json.NewDecoder从响应体中读取数据并反序列化为Message
	// 这里就是说明，后面的交流都用这个结构体了，否则不能正常解码
	if err := json.NewDecoder(resp.Body).Decode(&respMsg); err != nil {
		return nil, err
	}

	return respMsg.Data, nil
}

// 广播消息到所有节点
func (c *HttpClient) Broadcast(ctx context.Context, route uint32, data []byte) error {
	// 构建请求
	reqData, err := json.Marshal(Message{
		Route: route,
		Data:  data,
	})
	if err != nil {
		return err
	}
	//  发送HTTP POST请求到远程RPC服务的广播接口
	resp, err := c.client.Post(
		fmt.Sprintf("%s/rpc/broadcast", c.baseURL),
		"application/json",
		bytes.NewReader(reqData),
	)

	if resp.StatusCode != http.StatusOK {
		// 编译器瞎搞，只能这里多写一行了
		resp.Body.Close()
		return fmt.Errorf("broadcast failed with status: %s", resp.Status)
	}
	defer resp.Body.Close()
	return nil
}
